package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/coreos/etcd/clientv3"

	ncluster "nggs/cluster"
	netcdv3 "nggs/etcd/v3"
	nutil "nggs/util"
)

const (
	globalConfigFileNameWithoutExtend = ncluster.GlobalConfigName
	globalConfigFileName              = globalConfigFileNameWithoutExtend + ".json"
)

type config struct {
	ConfigDir string
	Services  map[string]interface{}
}

func (cfg *config) check() (err error) {
	if cfg.ConfigDir == "" {
		return fmt.Errorf("config dir must not empty")
	}
	cfg.ConfigDir, err = filepath.Abs(cfg.ConfigDir)
	if err != nil {
		return fmt.Errorf("invalid config dir, %s", err)
	}
	if len(cfg.Services) == 0 {
		return fmt.Errorf("service must not empty")
	}
	return nil
}

// 检查服务名是否合法, 必须是配置文件中存在的服务
func (cfg config) checkServiceName(name string) bool {
	if _, ok := cfg.Services[name]; !ok {
		if name != globalConfigFileNameWithoutExtend {
			return false
		}
	}
	return true
}

// 检查服务id是否合法, 必须是数字或者字符串"global"
func (cfg config) checkServiceID(id string) bool {
	_, err := strconv.Atoi(id)
	if err != nil && id != "global" {
		return false
	}
	return true
}

var gCfg = config{
	ConfigDir: "./config",
	Services:  map[string]interface{}{},
}

var gCommandList = map[string]func(){
	"ls":     ls,
	"l":      ls,
	"get":    get,
	"g":      get,
	"put":    put,
	"p":      put,
	"putall": putall,
	"pa":     putall,
	"del":    del,
	"clear":  clear,
	"watch":  watch,
	"w":      watch,
	"i":      instance,
	"ins":    instance,
}

func printHelp() {
	fmt.Printf(`使用方法:
    nctl 命令 [选项] [服务名] [服务id]
命令:
    ls
    - 功能: 罗列本地配置目录结构
    - 选项: 无

    get|g [|-e|-l|-i|-p] [服务名] [服务id]
    - 功能: 获取服务配置和状态
    - 选项:
    1.无|-e|-etcd	[服务名] [服务id]	获取etcd上的服务配置
    2.-l|-local		[服务名] [服务id]	查看本地服务配置
    3.-i|-instance	[服务名] [服务id]	查看etcd上的服务实例
    4.-p|-payload	[服务名] [服务id]	参看etcd上的服务负载

    put|p 服务名 [服务id]
    - 功能: 将本地服务配置写入etcd
    - 选项: 无

    putall|pa
    - 功能: 将所有服务配置写入etcd
    - 选项: 无

    del	服务名 服务id
    - 功能: 删除etcd上的服务配置
    - 选项: 无

    clear
    - 功能: 删除etcd上的数据
    - 选项: 无

    watch|w
    - 功能: 监控etcd变化
    - 选项: 无
`)
}

func parseParams() (option string, serviceName string, serviceID string, err error) {
	lenArgs := len(os.Args)
	if lenArgs < 3 {
		err = fmt.Errorf("param not enough")
		return
	}

	if os.Args[2][0] != '-' {
		if lenArgs >= 4 {
			serviceName = os.Args[2]
			serviceID = os.Args[3]
		} else if lenArgs >= 3 {
			serviceName = os.Args[2]
		}
		return
	}

	option = os.Args[2]

	if lenArgs >= 5 {
		serviceName = os.Args[3]
		serviceID = os.Args[4]
	} else if lenArgs >= 4 {
		serviceName = os.Args[3]
	}

	return
}

func walkLocal(
	before func(serviceName string) bool,
	fn func(serviceName string, serviceID string),
	after func(serviceName string),
) {
	err := filepath.Walk(gCfg.ConfigDir, func(path string, info os.FileInfo, err error) error {
		serviceName := info.Name()
		if serviceName == globalConfigFileName {
			dir, _ := filepath.Split(path)
			dir, _ = filepath.Abs(dir)
			if dir != gCfg.ConfigDir {
				// 排除子目录下的global.json
				return nil
			}
			fn(serviceName, "")
			return nil
		}

		if !gCfg.checkServiceName(serviceName) {
			// 跳过无关目录
			return nil
		}

		if before != nil {
			if !before(serviceName) {
				return nil
			}
		}

		err = filepath.Walk(filepath.Join(gCfg.ConfigDir, info.Name()), func(p string, i os.FileInfo, err error) error {
			isJson, err := filepath.Match("*.json", i.Name())
			if err != nil || !isJson {
				return nil
			}

			serviceID := strings.TrimSuffix(i.Name(), ".json")
			if !gCfg.checkServiceID(serviceID) {
				// 跳过文件名不是数字和global的配置
				return nil
			}

			if fn != nil {
				fn(serviceName, serviceID)
			}

			return nil
		})

		if after != nil {
			after(serviceName)
		}

		return nil
	})
	if err != nil {

	}
}

func checkData(data []byte) error {
	m := map[string]interface{}{}
	if err := json.Unmarshal(data, &m); err != nil {
		return err
	}
	return nil
}

func ls() {
	// 列出本地配置目录结构
	walkLocal(func(serviceName string) bool {
		fmt.Printf("%s\n- ", serviceName)
		return true
	}, func(serviceName string, serviceID string) {
		if serviceName == globalConfigFileName {
			fmt.Println(globalConfigFileName)
		} else {
			fmt.Printf("%s ", serviceID)
		}
	}, func(serviceName string) {
		fmt.Printf("\n")
	})
}

func getEtcdValue(section string, serviceName string, serviceID string, check bool) (kv map[string]string, err error) {
	var prefix string
	if serviceID != "" {
		prefix = ncluster.GenKey(ncluster.C.EtcdConfig().Root, section, serviceName, serviceID)
	} else {
		prefix = ncluster.GenKey(ncluster.C.EtcdConfig().Root, section, serviceName)
	}

	m, e := netcdv3.C.GetStrings(prefix)
	if e != nil {
		err = fmt.Errorf("netcdv3.C.GetStrings fail, prefix=%s, %w", prefix)
		return
	}

	kv = map[string]string{}
	for k, v := range m {
		if check {
			if e := checkData([]byte(v)); e != nil {
				err = fmt.Errorf("checkData fail, data=%s, %w", v, e)
				return
			}
		}
		kv[k] = v
	}
	return
}

func printEtcdValue(section string, serviceName string, serviceID string, check bool) {
	kv, err := getEtcdValue(section, serviceName, serviceID, check)
	if err != nil {
		fmt.Println(err)
		return
	}
	for k, v := range kv {
		fmt.Println(k)
		fmt.Println(v)
	}
}

func get() {
	option, serviceName, serviceID, err := parseParams()
	if err != nil {
		fmt.Println("参数错误")
		printHelp()
		return
	}

	if serviceName != "" {
		if !gCfg.checkServiceName(serviceName) {
			fmt.Println("服务名错误")
			return
		}
	}
	if serviceID != "" {
		if !gCfg.checkServiceID(serviceID) {
			fmt.Println("服务id错误")
			return
		}
	}

	switch option {
	case "", "-e", "-etcd":
		if serviceName != globalConfigFileNameWithoutExtend {
			printEtcdValue(ncluster.SectionConfig, serviceName, serviceID, true)
		} else {
			printEtcdValue(ncluster.SectionConfig, serviceName, "", true)
		}

	case "-i", "-instance":
		printEtcdValue(ncluster.SectionInstance, serviceName, serviceID, true)

	case "-p", "-payload":
		printEtcdValue(ncluster.SectionPayload, serviceName, serviceID, false)

	case "-l", "-local":
		if serviceName == globalConfigFileNameWithoutExtend {
			// 查看本地全局配置
			data, err := ioutil.ReadFile(filepath.Join(gCfg.ConfigDir, serviceName) + ".json")
			if err != nil {
				fmt.Printf("读取配置文件失败, %s\n", err)
				return
			}
			fmt.Printf("%s\n", ncluster.C.GetGlobalConfigKey())
			fmt.Printf("%s\n", data)
			if err := checkData(data); err != nil {
				fmt.Println(err)
			}
			return
		}

		if serviceID != "" {
			// 查看本地某个服务配置
			data, err := ioutil.ReadFile(filepath.Join(gCfg.ConfigDir, serviceName, serviceID) + ".json")
			if err != nil {
				fmt.Printf("读取配置文件失败, %s\n", err)
				return
			}
			fmt.Printf("%s\n", ncluster.C.GenConfigKeyS(serviceName, serviceID))
			fmt.Printf("%s\n", data)
			if err := checkData(data); err != nil {
				fmt.Println(err)
			}
			return
		}

		// 查看本地某种服务的所有配置
		walkLocal(func(svcName string) bool {
			if serviceName == "" {
				return true
			}
			if svcName != serviceName {
				return false
			}
			return true
		}, func(svcName string, serviceID string) {
			if svcName == globalConfigFileName {
				data, err := ioutil.ReadFile(filepath.Join(gCfg.ConfigDir, svcName))
				if err != nil {
					fmt.Printf("读取配置文件失败, %s\n", err)
					return
				}
				fmt.Printf("%s\n", ncluster.C.GetGlobalConfigKey())
				fmt.Printf("%s\n", data)
				if err := checkData(data); err != nil {
					fmt.Println(err)
				}
				return
			}
			data, err := ioutil.ReadFile(filepath.Join(gCfg.ConfigDir, svcName, serviceID) + ".json")
			if err != nil {
				fmt.Printf("读取配置文件失败, %s\n", err)
				return
			}
			fmt.Printf("%s\n", ncluster.C.GenConfigKeyS(svcName, serviceID))
			fmt.Printf("%s\n", data)
			if err := checkData(data); err != nil {
				fmt.Println(err)
			}
		}, nil)

	default:
		fmt.Println("选项错误")
		printHelp()
		return
	}
}

func putServiceConfig(serviceName string, serviceID string) error {
	// 将本地某个服务的配置写入etcd
	data, err := ioutil.ReadFile(filepath.Join(gCfg.ConfigDir, serviceName, serviceID) + ".json")
	if err != nil {
		fmt.Printf("读取配置文件失败, %s\n", err)
		return err
	}
	key := ncluster.C.GenConfigKeyS(serviceName, serviceID)
	fmt.Printf("%s\n", key)
	fmt.Printf("%s\n", data)
	if err := checkData(data); err != nil {
		fmt.Println(err)
		return err
	}
	if err := netcdv3.C.Put(key, string(data)); err != nil {
		fmt.Printf("将配置文件写入etcd失败, %s\n", err)
		return err
	}
	return nil
}

func putGlobalConfig() error {
	// 将全局配置写入etcd
	data, err := ioutil.ReadFile(filepath.Join(gCfg.ConfigDir, globalConfigFileName))
	if err != nil {
		fmt.Printf("读取配置文件失败, %s\n", err)
		return err
	}
	key := ncluster.C.GetGlobalConfigKey()
	fmt.Printf("%s\n", key)
	fmt.Printf("%s\n", data)
	if err := checkData(data); err != nil {
		fmt.Println(err)
		return err
	}
	if err := netcdv3.C.Put(key, string(data)); err != nil {
		fmt.Printf("将配置文件写入etcd失败, %s\n", err)
		return err
	}
	return nil
}

func put() {
	option, serviceName, serviceID, err := parseParams()
	if err != nil {
		fmt.Println("参数错误")
		printHelp()
		return
	}

	if !gCfg.checkServiceName(serviceName) {
		fmt.Println("服务名错误")
		return
	}

	switch option {
	case "":
		if serviceID != "" {
			// 将本地某个服务的配置写入etcd
			_ = putServiceConfig(serviceName, serviceID)
		} else {
			// 将本地某种服务的所有配置写入etcd
			walkLocal(func(svcName string) bool {
				if svcName != serviceName {
					return false
				}
				return true
			}, func(svcName string, serviceID string) {
				if svcName == globalConfigFileName {
					_ = putGlobalConfig()
					return
				}
				_ = putServiceConfig(serviceName, serviceID)
			}, nil)
		}

	default:
		fmt.Println("不支持选项")
		printHelp()
		return
	}
}

func putall() {
	walkLocal(nil, func(serviceName string, serviceID string) {
		if serviceName == globalConfigFileName {
			_ = putGlobalConfig()
			return
		}
		_ = putServiceConfig(serviceName, serviceID)
	}, nil)
}

func del() {
	option, serviceName, serviceID, err := parseParams()
	if err != nil {
		fmt.Println("参数错误")
		printHelp()
		return
	}

	if !gCfg.checkServiceName(serviceName) {
		fmt.Println("服务名错误")
		return
	}

	switch option {
	case "":
		var key string
		if serviceName == globalConfigFileNameWithoutExtend {
			key = ncluster.C.GetGlobalConfigKey()
		} else {
			key = ncluster.C.GenConfigKeyS(serviceName, serviceID)
		}
		n, err := netcdv3.C.DeleteWithPrefix(key)
		if err != nil {
			fmt.Printf("删除[%s]失败, %s\n", key, err)
			return
		}
		fmt.Println(n)
	default:
		fmt.Println("不支持选项")
		printHelp()
		return
	}
}

func clear() {
	n, err := netcdv3.C.DeleteWithPrefix(ncluster.C.EtcdConfig().Root)
	if err != nil {
		fmt.Printf("删除所有配置失败, %s\n", err)
		return
	}
	fmt.Println(n)
}

func watch() {
	err := ncluster.C.AddWatchCallback(func(err error, section string, serviceName string, serviceID int, ev *clientv3.Event) {
		if err != nil {
			log.Printf("%v, %v\n", err, ev)
		} else {
			fmt.Printf("%v\n", ev)
		}
	})
	if err != nil {
		// todo
	}
	nutil.WaitExitSignal()
}

func instance() {
	_, serviceName, serviceID, err := parseParams()
	if err != nil {
		fmt.Println("参数错误")
		printHelp()
		return
	}

	if serviceName != "" {
		if !gCfg.checkServiceName(serviceName) {
			fmt.Println("服务名错误")
			return
		}
	}
	if serviceID != "" {
		if !gCfg.checkServiceID(serviceID) {
			fmt.Println("服务id错误")
			return
		}
	}

	printEtcdValue(ncluster.SectionInstance, serviceName, serviceID, true)
}

func main() {
	programFileBaseName := nutil.GetProgramFileBaseName()
	configData, err := ioutil.ReadFile(programFileBaseName + ".json")
	if err == nil {
		err = json.Unmarshal(configData, &gCfg)
		if err != nil {
			log.Panicf("反序列化配置文件失败，%s", err)
		}
	}

	if len(gCfg.Services) == 0 {
		// 将config目录下的第一层子目录读取到Services中
		absConfigFilePath, err := filepath.Abs(gCfg.ConfigDir)
		if err != nil {
			log.Panicf("获取配置文件绝对路径失败，%s", err)
		}
		err = filepath.Walk(absConfigFilePath, func(path string, info os.FileInfo, err error) error {
			if path == absConfigFilePath {
				// 跳过配置文件目录
				return nil
			}
			if !info.IsDir() {
				// 跳过非目录路径
				return nil
			}
			if info.Name() == globalConfigFileNameWithoutExtend {
				return fmt.Errorf("服务名不能是global")
			}
			gCfg.Services[info.Name()] = struct{}{}
			return nil
		})
		if err != nil {
			log.Panicf("遍历配置文件目录失败，%s", err)
		}
	}

	err = gCfg.check()
	if err != nil {
		log.Panicf("检查配置文件失败，%s", err)
	}

	etcdConfigFilePath := filepath.Join(gCfg.ConfigDir, "etcd.json")
	err = ncluster.C.Init(etcdConfigFilePath, nil, nil)
	if err != nil {
		log.Panicf("初始化etcd失败, %s", err)
	}
	defer ncluster.C.Close()

	if len(os.Args) == 1 {
		printHelp()
		return
	}

	command := os.Args[1]
	handler, ok := gCommandList[command]
	if !ok {
		fmt.Println("命令错误")
		printHelp()
		return
	}

	handler()
}
